Los datos seleccionados corresponden a reportenes de crimenes (exceptuando asesinatos) que han ocurrido en la ciudad de Chicago, Estados Unidos, desde el año 2001 hasta el año 2017, los datos se encuentran disponibles en el sitio https://www.kaggle.com/currie32/crimes-in-chicago y estos fueron obtenidos del sistema del departamento de policia de Chicago, "CLEAR" (Citizen Law Enforcement Analysis and Reporting).
Este dataset fue elegido ya que al grupo le parece ideal para ejecutar tareas de clasificación, además se cuentan con una cantidad de datos lo suficientemente grande como para aplicar estas tareas en más de un periodo de tiempo definido, lo cual nos permitira saber si el comportamiento de los crimenes en Chicago a variado a lo largo de los años. Otro de los puntos que hizo que eligieramos estos datos fue el que los datos estan geolocalizados, por lo cual se puede trabajar con ellos de forma muy visual.
Nuestras aseveraciones iniciales previo a trabajar la data corresponden a los siguientes.
Existen relaciones entre las condiciones de un delito y el delito mismo.
Los focos donde se producen los crímenes varían según estas relaciones.
Estos patrones no son estáticos en el tiempo.
En base a nuestras hipótesis, se define el objetivo del trabajo, que corresponde a realizar tareas de clasificación que sean capaces de capturar estas diferencias entre los distintos tipos de crímenes. Posteriormente, se busca desarrollar modelos descriptivos y predictorios sobre estas clasificaciones, con el fin de poder detectar las posibles relaciones entre los crimenes y tambien poder predecir la ocurrencia de estos mismos.
Las siguientes son preguntas utilizadas como "baseline" para guiar el trabajo de análisis exploratorio de los datos:
¿Como ha cambiado el crimen en Chicago a lo largo de los años?.
¿Son ciertos tipos de crimenes mas propensos de ocurrir en ciertos sectores de la ciudad, o a cierta hora del día?.
¿Existe alguna correlación entre los distintos tipos de crimenes?.
¿Cuales son los crimenes predominantes en Chicago y si estos están relacionados entre sí?.
¿Que relación existe entre el tipo de crimen y el tipo de lugar donde se realizan?.
%matplotlib inline
# INSTALLS
!pip install pandas
!pip install -U -q PyDrive
!pip install scipy
!pip install xlrd
!pip install folium==0.6.0
!pip install geopandas
!pip install descartes
!pip install mlxtend
# Para trabajar la data
import matplotlib
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import os
import folium
from folium import plugins
import geopandas as gpd
import descartes
#from pandas import ExcelWriter
#from pandas import ExcelFile
#imports para poder usar los datos desde drive
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from google.colab import auth
from oauth2client.client import GoogleCredentials
# Descarga de archivos
## Nos debemos autentificar como usuarios
auth.authenticate_user()
gauth = GoogleAuth()
gauth.credentials = GoogleCredentials.get_application_default()
drive = GoogleDrive(gauth)
local_download_path = os.path.expanduser('~/data_drive')
try:
os.makedirs(local_download_path)
except: pass
folder_id = "1jr3rsT4w1XNJJyR4UUPeocUyyAgtDN7e" # id de la carpeta de google drive
# Descarga de archivos !
file_list = drive.ListFile(
{'q': "'{}' in parents".format(folder_id)}).GetList()
for f in file_list:
# Creamos los archivos y los descargamos
print('title: %s, id: %s' % (f['title'], f['id']))
fname = os.path.join(local_download_path, f['title'])
print('downloading to {}'.format(fname))
f_ = drive.CreateFile({'id': f['id']})
f_.GetContentFile(fname)
#path de los datos
directory = os.path.dirname(os.getcwd())
data_path = os.path.join(directory, "root/data_drive")
data2001_path = os.path.join(data_path, "Chicago_Crimes_2001_to_2004.csv")
data2005_path = os.path.join(data_path, "Chicago_Crimes_2005_to_2007.csv")
data2008_path = os.path.join(data_path, "Chicago_Crimes_2008_to_2011.csv")
data2012_path = os.path.join(data_path, "Chicago_Crimes_2012_to_2017.csv")
#crear los data frames
parse_dates = ['Date']
data2001 = pd.read_csv(data2001_path, error_bad_lines=False)
data2005 = pd.read_csv(data2005_path, error_bad_lines=False)
data2008 = pd.read_csv(data2008_path, error_bad_lines=False)
data2012 = pd.read_csv(data2012_path, error_bad_lines=False)
#juntar las bases de datos
dataconcat = pd.concat([data2012, data2008, data2005, data2001], ignore_index=False, axis=0)
del data2001
del data2005
del data2008
del data2012
Empezamos por ver el tamaño del dataset antes de ser modificado, para luego eliminar elementos cuyo id o numero de caso este duplicado y borrar columnas que no son utilizadas en el análisis posterior.
print('Tamaño inicial del dataset: ', dataconcat.shape)
#eliminacion de duplicados
dataconcat.drop_duplicates(subset=['ID', 'Case Number'], inplace=True)
#eliminar columnas innecesarias
dataconcat.drop(['Unnamed: 0', 'Case Number','Updated On','FBI Code','Beat','X Coordinate','Y Coordinate','Latitude','Longitude'], inplace=True, axis=1)
dataconcat.dropna()
print('Tamaño final luego de eliminar duplicados y columnas innecesarias: ', dataconcat.shape)
#dataconcat.head()
Se arreglan los tipos de datos para trabajar con ellos de manera mas sencilla
#arreglar los tipos de datos
data=dataconcat
data['ID'] = data['ID'].astype('category')
data['Primary Type'] = data['Primary Type'].astype('category')
data.IUCR = data.IUCR.astype('category')
data['coorX'] = data['Location'].str.split(',').str[0]
data['coorX']= data['coorX'].str.replace('(', '')
data['coorY'] = data['Location'].str.split(',').str[1]
data['coorY']= data['coorY'].str.replace(')', '')
data['coorX'] = pd.to_numeric(data['coorX'])
data['coorY'] = pd.to_numeric(data['coorY'])
data['District'] = data['District'].astype('category')
data['Community Area'] = data['Community Area'].astype('category')
# muy lento
import time
start = time.time()
data['Date']=pd.to_datetime(data['Date'])
finish = time.time()
print(finish - start)
data.head()
Luego de tener los datos en el formato deseado, se inicia el análisis exploratorio. Empezamos por investigar el tipo de datos que hay en cada columna.
data.info()
¿Cuáles son los crimenes que mas se han cometido en Chicago según los datos?
most_common_crimes = data.groupby('Primary Type').size().reset_index(name='count').sort_values(by=['count'], ascending=False)
most_common_crimes.plot(x='Primary Type',kind='barh', figsize=(8,10))
plt.gca().invert_yaxis()
plt.ylabel('Tipos de crimenes')
plt.xlabel('Frecuencia')
plt.title('Crimenes más comunes')
# los crimenes mas comunes
print ("Los 20 crimenes más comunes")
most_common_crimes = data.groupby(['Description','Primary Type']).size().reset_index(name='count').sort_values(by=['count'], ascending=False)
most_common_crimes_top20=most_common_crimes[0:20]
most_common_crimes_top20.plot(x=['Primary Type','Description'],kind='barh', figsize=(8,10))
plt.gca().invert_yaxis()
plt.ylabel('Descripcion (Tipo, Subtipo)')
plt.xlabel('Frecuencia')
Se puede apreciar que de las categorías generales, la mayor parte de los crimenes se concentran en una fracción de todas las categorías existentes en la base, siendo el mayor crimen el de "theft" (que corresponde a un robo que no involucra contacto con la persona a quien le roban). El segundo gráfico desglosa aun más como se dividen los crimenes al clasificarlos por su descripción secundaria (la cual especifica aun más el tipo de crimen).
¿En que lugares de la ciudad ocurren mayormente los crimenes?
lugar_comunes=data.groupby('Location Description').size().reset_index(name='count').sort_values(by=['count'], ascending=False).head(10)
lugar_comunes.index = range(1,len(lugar_comunes)+1)
print( "Tipos de lugares mas comunes")
lugar_comunes
¿Cómo ha evolucionado la cantidad de crimenes en el tiempo?
print("Gráfico, cantidad de crimenes por año")
data.groupby('Year').size().reset_index(name='count').plot(x='Year', y='count')
plt.xlabel('Año')
plt.ylabel('Frecuencia')
plt.title('Crimenes por año')
Este gráfico muestra a simple vista un descenso constante en la cantidad de crimenes por año. Se puede apreciar un numero particularmente bajo de crimenes para el 2004, considerando el comportamiento que tienen los años mas próximos a el. Se aprecia tambien que del 2015 al 2016 hubo un aumento de los crimenes por año en lugar de una disminución. Los datos del año 2017 no están completos y eso explica el descenso final de la línea.
¿La frecuencia de los crímenes cambia a lo largo del día?
data.groupby(data['Date'].map(lambda x: x.hour)).size().plot(x='hour', y='count')
plt.ylabel('Frecuencia')
plt.xlabel('Hora del día')
plt.title('Frecuencia de crímenes por hora de ocurrencia')
plt.show()
El gráfico muestra claras diferencias en la frecuencia de ocurrencia de crímenes dependiendo de la hora del día. La mayor cantidad de crímenes se concentra en el día y disminuye de forma significativa en la madrugada.
¿Se comportan los crímenes de la misma manera?
Luego de obtener el comportamiento general de los crímenes dependiendo de la hora del día, nuestro objetivo es ver si este comportamiento se mantiene igual al discernir por tipo de crimen. A continuación se muestra el mismo grafico anterior para los 8 tipos de crímenes con mayor ocurrencia en Chicago.
fig, axes = plt.subplots(nrows=4, ncols=2, figsize=(12,16))
axes[0,0].set_ylabel('crimenes tipo THEFT')
axes[0,0].set_xlabel('año')
axes[0,1].set_ylabel('crimenes tipo BATTERY')
axes[0,1].set_xlabel('año')
axes[1,0].set_ylabel('crimenes tipo CRIMINAL DAMAGE')
axes[1,0].set_xlabel('año')
axes[1,1].set_ylabel('crimenes tipo NARCOTICS')
axes[1,1].set_xlabel('año')
axes[2,0].set_ylabel('crimenes tipo OTHER OFFENSE')
axes[2,0].set_xlabel('año')
axes[2,1].set_ylabel('crimenes tipo ASSAULT')
axes[2,1].set_xlabel('año')
axes[3,0].set_ylabel('crimenes tipo BURGLARY')
axes[3,0].set_xlabel('año')
axes[3,1].set_ylabel('crimenes tipo MOTOR VEHICLE THEFT')
axes[3,1].set_xlabel('año')
grouped_data = data.groupby([data['Date'].map(lambda x: x.hour), data['Primary Type']]).size().reset_index(name='count').rename(index=str, columns={'Date': 'Hour'})
theft_data = grouped_data[grouped_data['Primary Type'] == 'THEFT']
theft_data.plot(x='Hour', y='count', ax=axes[0,0])
batt_data = grouped_data[grouped_data['Primary Type'] == 'BATTERY']
batt_data.plot(x='Hour', y='count', ax=axes[0,1])
cd_data = grouped_data[grouped_data['Primary Type'] == 'CRIMINAL DAMAGE']
cd_data.plot(x='Hour', y='count', ax=axes[1,0])
narc_data = grouped_data[grouped_data['Primary Type'] == 'NARCOTICS']
narc_data.plot(x='Hour', y='count', ax=axes[1,1])
oo_data = grouped_data[grouped_data['Primary Type'] == 'OTHER OFFENSE']
oo_data.plot(x='Hour', y='count', ax=axes[2,0])
assault_data = grouped_data[grouped_data['Primary Type'] == 'ASSAULT']
assault_data.plot(x='Hour', y='count', ax=axes[2,1])
burg_data = grouped_data[grouped_data['Primary Type'] == 'BURGLARY']
burg_data.plot(x='Hour', y='count', ax=axes[3,0])
mvt_data = grouped_data[grouped_data['Primary Type'] == 'MOTOR VEHICLE THEFT']
mvt_data.plot(x='Hour', y='count', ax=axes[3,1])
De los tipos de crímenes graficados, se observa que se mantiene la disminución de frecuencia en la madrugada, pero se observa que el comportamiento posterior es diferente entre tipos de crímenes, habiendo algunos que presentan peaks de ocurrencia alrededor de las 10 a 12 horas (como la categoría Burglary) y otros con peaks alrededor de las 20 a 22 horas (como la categoría Motor Vehicle theft).
El objetivo de las siguientes visualizaciones es de entender mejor como se distribuyen los crimenes en toda el area de la ciudad de Chicago, mediante diferentes representaciones es en el mapa de la ciudad.
conteo = data['Location'].value_counts()
df_conteo = pd.DataFrame({"Location" : conteo.index, "ValueCount":conteo})
df_conteo.index = range(len(df_conteo))
df_conteo['coorX'] = df_conteo['Location'].str.split(',').str[0]
df_conteo['coorX']= df_conteo['coorX'].str.replace('(', '')
df_conteo['coorY'] = df_conteo['Location'].str.split(',').str[1]
df_conteo['coorY']= df_conteo['coorY'].str.replace(')', '')
df_conteo = df_conteo.drop(columns=['Location'], axis = 1)
chicago_map_crime = folium.Map(location=[41.895140898, -87.624255632],
zoom_start=13,
tiles="Stamen Terrain")
for i in range(1000):
lat = float(df_conteo['coorY'].iloc[i])
long = float(df_conteo['coorX'].iloc[i])
radius = df_conteo['ValueCount'].iloc[i] / 100
if df_conteo['ValueCount'].iloc[i] > 1000:
color = "#FF4500"
else:
color = "#008080"
popup_text = """Latitude : {}<br>
Longitude : {}<br>
Criminal Incidents : {}<br>"""
popup_text = popup_text.format(lat,
long,
df_conteo['ValueCount'].iloc[i]
)
folium.CircleMarker(location = [long, lat], popup= popup_text,radius = radius, color = color, fill = True).add_to(chicago_map_crime)
print("1000 Puntos con más crímenes en Chicago")
chicago_map_crime
from folium import plugins
heat_map= folium.Map(location=[41.895140898, -87.624255632],
zoom_start=11.5,
tiles="Stamen Terrain")
data.dropna()
location=[]
for i in range(50000):
lat = float(data['coorY'].iloc[i])
long = float(data['coorX'].iloc[i])
loc=(long,lat)
if np.isfinite(lat):
location.append(loc)
print ("Mapa de calor de los primeros 50.000 puntos")
heat_map.add_children(plugins.HeatMap(location, radius=15))
heat_map
De los mapas se puede concluir que gran parte de los crimenes de chicago se producen en el aeropuerto "O'hare", cerca del "Millenium park", por la zona de "Hometown", los alrededores de "Garfield park" y por toda el área costera central mayoritariamente.
Mapa por distrito
La base de datos cuenta con varias divisiones geográficas que se utilizan en la ciudad de Chicago (las variables Block, District y Ward), arbitrariamente se elige graficar la cantidad de crímenes por distrito existente, lo cual entrega el mapa a continuación. Adicionalmente, se grafica en este caso sólo crimenes que ocurrieron el 2016.
#Cantidad de crímenes por distrito de la Ciudad
chicago_map_crime = folium.Map(location=[41.895140898, -87.624255632], zoom_start=10, tiles="Stamen Terrain")
distritos_path = os.path.join(data_path, "Boundaries - Police Districts (current).geojson")
#distritos = gpd.read_file(distritos_path)
conteodist=data.loc[:,['Primary Type','District','Year']]
#conteodist=data[['Primary Type','District','Year']]
conteodist['District'] = conteodist['District'].astype('int')
conteodist['District'] = conteodist['District'].astype('str')
conteodist=conteodist.loc[conteodist['Year'] == 2016]
conteodist=conteodist.groupby('District').size().reset_index(name='count').sort_values(by=['count'], ascending=False)
#conteodist['Count'] = conteodist['Count'].astype('str')
#conteodist=conteodist.drop(index=0)
chicago_map_crime.choropleth(geo_data = distritos_path, name ='distritos',
data = conteodist, columns = ['District', 'count'],
key_on = 'feature.properties.dist_num', fill_color = 'YlOrRd',
legend_name = 'Cantidad de crímenes',threshold_scale=[0,100, 3000, 8000, 12000, 15000, 18000])
folium.LayerControl().add_to(chicago_map_crime)
chicago_map_crime
#conteodist
#highlight = True
Para el año 2016, se pueden ver grandes variaciones entre distritos, habiendo distritos con una frecuencia entre 0 y 3 mil crimenes versus otros con frecuencias superiores a los 12 mil crímenes.
A continuación se buscan posibles outliers sobre las coordenadas en donde se realiza el crimen, con el objetivo de eliminar datos muy diferentes al resto.
data['coorX'] = data['coorX'].astype('float')
data['coorY'] = data['coorY'].astype('float')
#boxplots sobre las coordenas de los crimenes
green_diamond = dict(markerfacecolor='g', marker='D') #para ver los outliers y de forma bonita
fig, (ax1,ax2) = plt.subplots(1,2, figsize=(10,6))
boxp1 = data.boxplot(column='coorX',showfliers=True, flierprops=green_diamond, ax=ax1)
boxp2 = data.boxplot(column='coorY',showfliers=True, flierprops=green_diamond, ax=ax2)
#boxp3 = data.boxplot(column='coorX',showfliers=False, ax=ax3)
#boxp4 = data.boxplot(column='coorY',showfliers=False, ax=ax4)
plt.tight_layout()
De los gráficos se puede apreciar que existe un valor tanto para la longitud (coorX) como la latitud (coorY) que se escapan de forma significativa del resto de los datos, por lo cual se decide eliminar estos valores del dataset, siendo el motivo principal para esta desición el evitar que estos puntos perjudiquen una posible normalización futura de estos valores, o que perjudiquen a la vez los seleccionadores de clusteres en lo que sigue del trabajo. La distribución de los datos sin los outliers mas extremos queda como se ve a continuación.
#Eliminar outlier
data = data[data.coorX > 37]
data = data[data.coorY > -91]
green_diamond = dict(markerfacecolor='g', marker='D') #para ver los outliers y de forma bonita
fig, (ax1,ax2) = plt.subplots(1,2, figsize=(10,6))
boxp1 = data.boxplot(column='coorX',showfliers=True, flierprops=green_diamond, ax=ax1)
boxp2 = data.boxplot(column='coorY',showfliers=True, flierprops=green_diamond, ax=ax2)
plt.tight_layout()
En cuanto al resto de valores identificados como outliers en la latitud, estos se conservaran por no ser tan distantes como los que se vieron en el paso previo y por que ya no es claro si se ganará algo o no con eliminar estos casos.
#install e imports necesarios para esta parte del proyecto
import sklearn
from sklearn.cluster import KMeans, DBSCAN
from sklearn.model_selection import cross_val_score
from sklearn import metrics, preprocessing
from folium import plugins
Nuestro primer acercamiento para hacer cluster sobre los crimenes en chicago corresponde a la utilización de la técnica K-means, debido a que es ideal para trabajar con datos geolocalizados ya que minimiza el SSE.
La intuición detrás de la utilización de K-means va de la mano con encontrar diferencias significativas en el comportamiento de los delitos en Chicago según las zonas geografica de la ciudad.
relevant_data = data[data['Primary Type'] != 'OTHER OFFENSE'].dropna()
relevant_crimes = relevant_data.groupby('Primary Type').size().reset_index(name='count').sort_values(by=['count'], ascending=False)[:15]
relevant_crimes = relevant_crimes['Primary Type']
relevant_data = relevant_data[relevant_data['Primary Type'].isin(relevant_crimes)]
relevant_data['Primary Type'] = relevant_data['Primary Type'].cat.remove_unused_categories()
data_sample = relevant_data.sample(frac=0.1).reset_index(drop=True)
El primer acercamiento con K-means incluye únicamente las coordenadas para definir los distintos clusters. El primer paso es definir el K ideal mediante la inspección de la variación de la suma de los errores cuadraticos según este valor. En este caso el K ideal es 5
sse = {}
for k in range(1, 10):
kmeans = KMeans(n_clusters=k, max_iter=1000).fit(data_sample[['coorX', 'coorY']])
sse[k] = kmeans.inertia_
plt.figure()
plt.plot(list(sse.keys()), list(sse.values()))
plt.xlabel("Number of cluster")
plt.ylabel("SSE")
plt.show()
A continuación se realiza el algoritmo y se procede a graficar en un mapa los primeros 10.000 puntos de cada clusters. Se puede apreciar que efectivamente la técnica K-means divide el mapa en 5 zonas de igual densidad global.
# probemos con 5
kmeans = KMeans(n_clusters=5, max_iter=1000).fit(data_sample[['coorX', 'coorY']])
data_sample["cluster"] = kmeans.labels_
data_sample['coorX'] = pd.to_numeric(data_sample['coorX'])
data_sample['coorY'] = pd.to_numeric(data_sample['coorY'])
c0 = data_sample[data_sample['cluster'] == 0]
c1 = data_sample[data_sample['cluster'] == 1]
c2 = data_sample[data_sample['cluster'] == 2]
c3 = data_sample[data_sample['cluster'] == 3]
c4 = data_sample[data_sample['cluster'] == 4]
hm0= folium.Map(location=[41.8, -87.6],
zoom_start=10.5,
tiles="Stamen Terrain")
n_points = min(c0.shape[0], 5000)
location=[]
for i in range(n_points):
lat = c0['coorY'].iloc[i]
long = c0['coorX'].iloc[i]
loc=(long, lat)
if np.isfinite(lat):
location.append(loc)
hm0.add_child(plugins.HeatMap(location, radius=15, gradient={.4:'blue', .65:'blue', 1:'blue'}))
n_points = min(c1.shape[0], 5000)
location=[]
for i in range(n_points):
lat = c1['coorY'].iloc[i]
long = c1['coorX'].iloc[i]
loc=(long, lat)
if np.isfinite(lat):
location.append(loc)
hm0.add_child(plugins.HeatMap(location, radius=15, gradient={.4:'yellow', .65:'yellow', 1:'yellow'}))
n_points = min(c2.shape[0], 5000)
location=[]
for i in range(n_points):
lat = c2['coorY'].iloc[i]
long = c2['coorX'].iloc[i]
loc=(long, lat)
if np.isfinite(lat):
location.append(loc)
hm0.add_child(plugins.HeatMap(location, radius=15, gradient={.4:'red', .65:'red', 1:'red'}))
n_points = min(c3.shape[0], 5000)
location=[]
for i in range(n_points):
lat = c3['coorY'].iloc[i]
long = c3['coorX'].iloc[i]
loc=(long, lat)
if np.isfinite(lat):
location.append(loc)
hm0.add_child(plugins.HeatMap(location, radius=15, gradient={.4:'green', .65:'green', 1:'green'}))
n_points = min(c4.shape[0], 5000)
location=[]
for i in range(n_points):
lat = c4['coorY'].iloc[i]
long = c4['coorX'].iloc[i]
loc=(long, lat)
if np.isfinite(lat):
location.append(loc)
hm0.add_child(plugins.HeatMap(location, radius=15, gradient={.4:'purple', .65:'purple', 1:'purple'}))
hm0
Como se puede apreciar a continuación, a exepción del cluster número 4 no existe una diferencia notoria a simple vista entre los distintos clusters, debido a que la distribución de los distintos delitos es (en general) similar.
Por lo tanto en este caso la técnica K-means no nos entrega información novedosa respecto a zonas más propensas al delito que otras además de que el cluster 4 se caracteriza por poseer menos delitos que los demás.
fig, axes = plt.subplots(nrows=3, ncols=2, figsize=(100,60))
data_sample[data_sample['cluster'] ==0].groupby(['Primary Type']).size().reset_index(name='count').sort_values(by=['Primary Type'], ascending=False).plot(x='Primary Type',kind='barh', figsize=(15,15),label=1 ,ax=axes[0,0])
data_sample[data_sample['cluster'] ==1].groupby(['Primary Type']).size().reset_index(name='count').sort_values(by=['Primary Type'], ascending=False).plot(x='Primary Type',kind='barh', figsize=(15,15),label=2 ,ax=axes[0,1])
data_sample[data_sample['cluster'] ==2].groupby(['Primary Type']).size().reset_index(name='count').sort_values(by=['Primary Type'], ascending=False).plot(x='Primary Type',kind='barh', figsize=(15,15),label=3 ,ax=axes[1,0])
data_sample[data_sample['cluster'] ==3].groupby(['Primary Type']).size().reset_index(name='count').sort_values(by=['Primary Type'], ascending=False).plot(x='Primary Type',kind='barh', figsize=(15,15),label=4 ,ax=axes[1,1])
data_sample[data_sample['cluster'] ==4].groupby(['Primary Type']).size().reset_index(name='count').sort_values(by=['Primary Type'], ascending=False).plot(x='Primary Type',kind='barh', figsize=(15,15),label=5 ,ax=axes[2,0])
plt.tight_layout()
A continuación se vuelve a utilizar la técnica K-means con al excepción de que en esta ocación se utiliza además la variables correspondiente al tipo de crimen ocurrido.
# se toma una muestra de los datos y se formatea para realizar clusters
data_sample = relevant_data.sample(frac=0.1)
data_sample['Date'] = pd.to_datetime(data_sample['Date'])
data_sample['Hour'] = [d.hour for d in data_sample['Date']]
data_sample['Month'] = [d.month for d in data_sample['Date']]
data_to_cluster = data_sample.drop(['ID', 'Date', 'Block', 'IUCR', 'Description',
'Location Description', 'Arrest', 'Domestic',
'District', 'Ward', 'Community Area', 'Location',
'Year', 'Month', 'Hour'], axis=1)
type_weight = 1
data_to_cluster = pd.get_dummies(data_to_cluster, columns=["Primary Type"])
column_names = data_to_cluster.columns.values
coor_data = data_to_cluster[['coorX', 'coorY']].values
type_data = data_to_cluster.drop(['coorX', 'coorY'], axis=1).values
min_max_scaler = preprocessing.MinMaxScaler()
coor_data = min_max_scaler.fit_transform(coor_data)
data_to_cluster = np.concatenate((coor_data, type_data * type_weight), axis=1)
data_to_cluster = pd.DataFrame(data_to_cluster, columns=column_names)
En este ocación se puede notar que desde K=15 la disminución de la suma de los errores cuadraticos de existir es inperceptible, y se probó con este número primerro, sin embargo, al utilizar K=15 nos percatamos de que K-means solo separaba cada tipo de crimen en un cluster diferente, por lo que se decidio utilizar k=30 para intentar apreciar otros tipos de agrupación. El mapa y tabla siguiente muestran el resultado para uno de los cluster utilizando este último k (esto para no ocupar tanto espacio en muchos mapas de calor).
sse = {}
for k in range(1, 30):
kmeans = KMeans(n_clusters=k, max_iter=1000).fit(data_to_cluster)
sse[k] = kmeans.inertia_
plt.figure()
plt.plot(list(sse.keys()), list(sse.values()))
plt.xlabel("Number of cluster")
plt.ylabel("SSE")
plt.show()
k = 30
kmeans = KMeans(n_clusters=k, max_iter=1000).fit(data_to_cluster)
data_sample["cluster"] = kmeans.labels_
clusters = []
for i in range(k):
clusters.append(data_sample[data_sample['cluster'] == i])
hm= folium.Map(location=[41.8, -87.6],
zoom_start=10.5,
tiles="Stamen Terrain")
index = 4
n_points = min(clusters[index].shape[0], 10000)
location=[]
for i in range(n_points):
lat = clusters[index]['coorY'].iloc[i]
long = clusters[index]['coorX'].iloc[i]
loc=(long, lat)
if np.isfinite(lat):
location.append(loc)
hm.add_child(plugins.HeatMap(location, radius=15))
hm
Como se puede apreciar en la siguiente tabla, K-mean aún entrega como ouputs clusters que se caracterizan por poseer un único crimen, esto debido a que K-means se trata de un metodo de clusterización particional que asocia cada punto a un centroide. Por lo tanto no es la mejor técnica si lo que se busca es descubrir un patrón en la aparición de los crimenes.
clusters[3].groupby('Primary Type').size().reset_index(name='count').sort_values(by=['count'], ascending=False)
do En lo que sigue se hara uso de la técnica de clusterización DBSCAN que se caracteriza por ser un cluster basado en densidad, esto nos permitira a traves de DBSCAN diferenciar las zonas de alta densidad de crimen de aquellas donde han ocurrido crimenes esporádicos, pues como se aprecía en el EDA, en prácticamente toda la ciudad se han cometido crimenes.
Otro de las ventajas de ocupar un metodo de clusterización basado en densidad como DBSCAN es que nos permite filtrar la data y obtener distintos clusters, como se vera a continuación, al tomar los datos de un tipo específico de crimen a distintas horas del día aparecen distintos clusters. Esto nos da pistas sobre el comportamiento de un delito a lo largo del día.
A continuación se trabajara exclusivamente con los datos de los delitos asociados a narcoticos utilizando la técnica DBSCAN durante distintos periodos del día (día,mañana y noche).
data_sample = relevant_data.sample(frac=0.05)
data_sample['Date'] = pd.to_datetime(data_sample['Date'])
data_sample['Hour'] = [d.hour for d in data_sample['Date']]
data_sample['Month'] = [d.month for d in data_sample['Date']]
narcotics = data_sample[data_sample['Primary Type'] == 'NARCOTICS']
narcotics_day = narcotics[(narcotics['Hour'] > 9) & (narcotics['Hour'] <= 17)]
narcotics_night = narcotics[(narcotics['Hour'] > 17) | (narcotics['Hour'] <= 1)]
narcotics_morning = narcotics[(narcotics['Hour'] > 1) & (narcotics['Hour'] <= 9)]
narcotics_night.shape
Lo que procede será realizar DBSCAN para cada uno de los subconjuntos de los crimenes asociados de narcoticos, luego lo que se procederá será graficar mapas de calor con los puntos pertenecientes a cada uno de los clusters
db = DBSCAN(eps=0.007, min_samples=100).fit(narcotics_night[['coorX', 'coorY']])
k = len(set(db.labels_)) - (1 if -1 in db.labels_ else 0)
narcotics_night['cluster'] = db.labels_
clusters_night = []
for i in range(k):
clusters_night.append(narcotics_night[narcotics_night['cluster'] == i])
k
hm= folium.Map(location=[41.8, -87.6],
zoom_start=10.5,
tiles="Stamen Terrain")
for cluster in clusters_night:
n_points = min(cluster.shape[0], 10000)
location=[]
for i in range(n_points):
lat = cluster['coorY'].iloc[i]
long = cluster['coorX'].iloc[i]
loc=(long, lat)
if np.isfinite(lat):
location.append(loc)
hm.add_child(plugins.HeatMap(location, radius=15))
hm
db = DBSCAN(eps=0.008, min_samples=100).fit(narcotics_day[['coorX', 'coorY']])
k = len(set(db.labels_)) - (1 if -1 in db.labels_ else 0)
narcotics_day['cluster'] = db.labels_
clusters_day = []
for i in range(k):
clusters_day.append(narcotics_day[narcotics_day['cluster'] == i])
k
hm= folium.Map(location=[41.8, -87.6],
zoom_start=10.5,
tiles="Stamen Terrain")
for cluster in clusters_day:
n_points = min(cluster.shape[0], 10000)
location=[]
for i in range(n_points):
lat = cluster['coorY'].iloc[i]
long = cluster['coorX'].iloc[i]
loc=(long, lat)
if np.isfinite(lat):
location.append(loc)
hm.add_child(plugins.HeatMap(location, radius=15))
hm
db = DBSCAN(eps=0.008, min_samples=100).fit(narcotics_morning[['coorX', 'coorY']])
k = len(set(db.labels_)) - (1 if -1 in db.labels_ else 0)
narcotics_morning['cluster'] = db.labels_
clusters_morning = []
for i in range(k):
clusters_morning.append(narcotics_day[narcotics_day['cluster'] == i])
k
hm= folium.Map(location=[41.8, -87.6],
zoom_start=10.5,
tiles="Stamen Terrain")
for cluster in clusters_morning:
n_points = min(cluster.shape[0], 10000)
location=[]
for i in range(n_points):
lat = cluster['coorY'].iloc[i]
long = cluster['coorX'].iloc[i]
loc=(long, lat)
if np.isfinite(lat):
location.append(loc)
hm.add_child(plugins.HeatMap(location, radius=15))
hm
El uso de DBSCAN permitio descubrir distintas zonas de concentración de los delitos asociados a narcoticos en la ciudad de chicago. Además se puede ver que existe una zona en particular en la que se producen este tipo de delitos a toda hora del día.
Lo anterior sugiere que DBSCAN es al técnica más apropiada para trabajar con este tipo de datos debido a que se basa en la densidad para determinar los clusters, por lo que no es sensible al ruido.
A partir nuestras resultados en el primer acercamiento a DBSCAN, se decide seguir buscando cluster con la implementación de éste método.
Nuestro objetivo ahora es ver como evolucionan los cluster de crímenes a través de los años. Si bien ya sabemos del análisis exploratorio inicial que los crímenes disminuyen a lo largo del tiempo, queremos ver el comportamiento de esta disminución, si es uniforme en toda la ciudad o no . Para lo cual se repite el método de clasificación utilizando los datos del 2001, 2006, 2011 y 2015, específicamente para los tipos de crimen Narcotics y Assault, ya que estos crimenes se encuentran dentro de los 6 más recurrentes según el análisis exploratorio previo..
def plot_crime_year_clusters(crime, year, data, eps=0.008, min_samples=200):
# data = data.copy()
if crime:
data = data[data['Primary Type'] == crime]
if year:
data = data[data['Year'] == year]
db = DBSCAN(eps=eps, min_samples=min_samples).fit(data[['coorX', 'coorY']])
k = len(set(db.labels_)) - (1 if -1 in db.labels_ else 0)
data['cluster'] = db.labels_
clusters = []
for i in range(k):
clusters.append(data[data['cluster'] == i])
hm= folium.Map(location=[41.8, -87.6],
zoom_start=10.5,
tiles="Stamen Terrain")
for cluster in clusters:
n_points = min(cluster.shape[0], 10000)
location=[]
for i in range(n_points):
lat = cluster['coorY'].iloc[i]
long = cluster['coorX'].iloc[i]
loc=(long, lat)
if np.isfinite(lat):
location.append(loc)
hm.add_child(plugins.HeatMap(location, radius=15))
return hm
Comenzamos con el crimen assault, con el objetivo de ver como disminuye este tipo de crimen.
plot_crime_year_clusters('ASSAULT', 2001, data, eps=0.008, min_samples=150)
Para el año 2001 se puede apreciar que los cluster encontrados son grandes tanto en densidad como en el área de la ciudad que cubren, estando presentes en casi toda la ciudad.
plot_crime_year_clusters('ASSAULT', 2006, data, eps=0.008, min_samples=150)
Se aprecia una leve disminución de la canitdad de crímenes en los cluster, donde las mayores disminuciones se ven en ciertos puntos de la costa que dejaron de aparecer dentro de la selección.
plot_crime_year_clusters('ASSAULT', 2011, data, eps=0.008, min_samples=150)
La disminución de crímenes es mucho mas notoria desde el 2006 al 2011, donde los dos focos interiores de la ciudad con alta densidad se han reducido notoriamente, similar a la parte norte de la costa, donde solo queda un punto con concentración alta de este tipo de crímenes.
plot_crime_year_clusters('ASSAULT', 2015, data, eps=0.008, min_samples=150)
Para este último gráfico, se ve que se ha reducido la cantida de crimenes en cada cluster, pero de manera leve en comparacion al cambio 2006 - 2011. Los mismos sectores de mayor densidad vistos para el año 2011 se mantienen todos para el año 2015, no pudiendo lograr despejar la costa por completo de este tipo de crímenes.
A continuación se muestran los resultados para el análisis del crimen narcóticos para los mismos años.
plot_crime_year_clusters('NARCOTICS', 2001, data)
La concentración y área que cubren los cluster para narcóticos en el 2001 es también bastante alta, presentando este crimen una leve mayor concentración en la zona sur de la ciudad, en comparación con el caso anterior para el mismo año.
plot_crime_year_clusters('NARCOTICS', 2006, data)
Principalmente se aprecian leves disminuciones del tamaño de los cluster, desapareciendo ya ciertos sectores por el norte de chicago.
plot_crime_year_clusters('NARCOTICS', 2011, data)
El mayor trabajo o efectividad en la disminución del crimen parece estar concentrado a lo largo de toda la costa y en el sector más al sur de la ciudad, los cuales parecen ir disminuyendo de "afuera hacia dentro", en vez de ir disminuyendo uniformemente a lo largo de todo el territorio de la ciudad.
plot_crime_year_clusters('NARCOTICS', 2015, data)
Practicamente toda la costa a excepción de un par de lugares dejaron de aparecer como cluster, ocurriendo lo mismo para todo el sector sur de Chicago. Este crimen presenta una disminución mucho mayor en cuanto a densidad y área que cubren las zonas de alta concentración, a diferencia del caso anterior. También es este periodo entre el 2011 al 2015 el que presentó la mayor disminución, en ves de ser el periodo 2006 - 2011 como el caso anterior, lo que pueden ser indicios de que los esfuerzos de disminución de crimenes pueden no ser uniformes para todos los tipos, si no que han ido rotando en cuanto al foco de tipos de crímenes que se busca disminuir en la ciudad.
Los análisis realizados con DBSCAN resultaron muy útiles para ver de manera gráfica la forma en que los distintos focos de crímenes específicos (en este caso narcoticos y assault) varían a lo largo de los años.
Sin embargo se cree que aún pueden haber relaciones no encontradas por el método utilizado, por lo cual se decide trabajar los datos de manera diferente, ahora utilizando reglas de asociación, con el objetivo de encontrar nuevas relaciones entre los datos disponibles. Específicamente se quiere encontrar relaciones entre el espacio, tiempo, el tipo de crimen y la descripción del lugar donde ocurre este, con la diferencia de que ahora se utilizarán datos agregados.
Las agrupaciones realizadas a los datos son:
Para poder trabajar con reglas de asociación, el formato de los datos debió ser transformados, proceso en el cual se eliminaron todas las columnas que no se utilizan en las partes posteriores, y cada fila del dataset paso a ser una "transacción". En una transacción todos los posibles valores que podían tomas las columnas elegidas en el dataset original pasaron a ser "items" que puede contener una transacción. Así, una transacción corresponde a un subconjunto del itemset que resulta de la unión de todos estos posibles valores.
from mlxtend.frequent_patterns import apriori, association_rules
data_sample = data
data_sample['Date'] = pd.to_datetime(data_sample['Date'])
data_sample['Hour'] = [d.hour for d in data_sample['Date']]
data_sample['Month'] = [d.month for d in data_sample['Date']]
import calendar
data_sample['Month'] = data_sample['Month'].apply(lambda x: calendar.month_abbr[x])
def day_period(x):
if x > 9 and x <= 17:
return 'day'
elif x > 17 or x <= 1:
return 'night'
else:
return 'morning'
def season(x):
if x in ['Dec', 'Jan', 'Feb']:
return 'winter'
elif x in ['Mar', 'Apr', 'May']:
return 'spring'
elif x in ['Jun', 'Jul', 'Aug']:
return 'summer'
elif x in ['Sep', 'Oct', 'Nov']:
return 'fall'
data_sample['Day period'] = data_sample['Hour'].apply(day_period)
data_sample['Season'] = data_sample['Month'].apply(season)
input_data = data_sample.drop(['ID', 'Date', 'Block', 'IUCR', 'Location', 'Description',
'coorX', 'coorY', 'Ward', 'Community Area', ], axis=1)
input_data['Year']=pd.to_numeric(input_data['Year'], downcast='integer')
#input_data = input_data[['Year', 'Month','Hour','District','Location Description','Primary Type']]
input_data = input_data[['Year', 'Month', 'Day period', 'District','Location Description','Primary Type','Hour','Season']]
input_data.head()
delitos=['Primary Type_ASSAULT', #0
'Primary Type_BATTERY', #1
'Primary Type_BURGLARY', #2
'Primary Type_CRIM SEXUAL ASSAULT', #3
'Primary Type_CRIMINAL DAMAGE', #4
'Primary Type_CRIMINAL TRESPASS', #5
'Primary Type_DECEPTIVE PRACTICE', #6
'Primary Type_LIQUOR LAW VIOLATION', #7
'Primary Type_MOTOR VEHICLE THEFT', #8
'Primary Type_NARCOTICS', #9
'Primary Type_OTHER OFFENSE', #10
'Primary Type_PUBLIC PEACE VIOLATION', #11
'Primary Type_ROBBERY', #12
'Primary Type_SEX OFFENSE', #13
'Primary Type_THEFT', #14
'Primary Type_WEAPONS VIOLATION'] #15
def reglas_anuales(año, delito):
sample=input_data
sample["Hour"] = sample["Hour"].astype('str')
sample["District"] = sample["District"].astype('str')
sample=sample[(sample['Year'] == año)]
basket_set=pd.get_dummies(sample)
sample=sample[['Day period', 'District', 'Primary Type']]
basket_set=pd.get_dummies(sample)
frequent_itemsets = apriori(basket_set, min_support=0.01, use_colnames=True)
rules = association_rules(frequent_itemsets, metric="lift", min_threshold=1)
rules['length'] = rules['antecedents'].apply(lambda x: len(x))
#rules=rules[ (rules['length'] >= 2) ]
rules.head()
frequent_itemsets
reglas_utiles=pd.DataFrame()
crimen=set(sample['Primary Type'].unique())
for i in crimen:
a=("Primary Type_"+i)
aux=rules[ (rules['consequents'] == {a}) ]
reglas_utiles=reglas_utiles.append(aux)
reglas_utiles=reglas_utiles.sort_values(['lift','antecedents'], ascending=[0,1])
reglas_utiles=reglas_utiles[(reglas_utiles['consequents']=={delito})]
años_col=pd.DataFrame({'Año': np.ones(len(reglas_utiles.index), dtype=np.int)*año}).astype('str')
print(len(reglas_utiles.index))
print(años_col)
reglas_utiles=reglas_utiles.join(años_col)
reglas_utiles['Año']=año
print("Reglas para año " + str(año) + ",delito:"+ delito)
return reglas_utiles
años=[2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016]
#robos
dicc={}
for i in delitos:
aux=pd.DataFrame()
for j in años:
a=reglas_anuales(j,i)
aux=aux.append(a)
dicc[i]=aux
#Dic contiene el df con las reglas de asociación para cada delito
A continuación se presentan las reglas anuales para cada tipo de delito, se puede apreciar que el support de las teglas es bajo (~0,015), esto es debido a la atomizados de los delitos en la ciudad de chicago. Sin embargo se puede apreciar que se repite para distintos años eel mismo antecedente correspondiente a los distritos.
#Para ver otros delitos cambiar el índice del diccionario delitos.
dicc[delitos[1]].sort_values(['lift','antecedents'], ascending=[0,1])
En este caso se análiza la concurrencia entre un tipo de delitos, la hora en la que ocurre y el lugar donde se comete, en está ocasión las reglas que se obtienen son bastante intuitivos, redundantes y/o inútiles ya que se obtienen reglas como :
Esto muestra la importancia de filtrar las reglas para evitar aquellas que no entregan información valiosa.
new_basket = pd.get_dummies(input_data[['Season', 'Day period', 'District', 'Location Description', 'Primary Type']])
new_itemsets = apriori(new_basket, min_support=0.01, use_colnames=True)
new_rules = association_rules(new_itemsets, metric="lift", min_threshold=1)
new_rules["antecedent_len"] = new_rules["antecedents"].apply(lambda x: len(x))
new_rules[new_rules['antecedent_len'] >= 2].sort_values(['lift'], ascending=[0])
pd.set_option('display.max_colwidth', -1)
pd.set_option('display.max_rows', 500)
new_rules[new_rules['antecedent_len'] >= 2].sort_values(['lift'], ascending=[0])[['antecedents', 'consequents', 'support', 'confidence', 'lift']]
new_rules[new_rules['antecedent_len'] >= 2].sort_values(['lift'], ascending=[0])[['antecedents', 'consequents', 'support', 'confidence', 'lift']].loc[[602, 664, 626, 658, 504, 420]]
# ejemplos de casos con un antecedente
new_rules.sort_values(['lift'], ascending=[0])[0:20]
Luego de haber realizado el proyecto de minería de datos se pueden extraer las siguientes conclusiones:
A partir de estas conclusiones se pueden extraer las siguientes sugerencias: